#!/usr/bin/python2.4
#
# Copyright 2010 Google Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""A collection of basic genetic operators for two-archive algorithm.

Contains basic genetic operators required by the two-archive
algorithm: single-point crossover, bit-flip mutation, and
a random selection from convergence/diversity archives.

TwoArchiveSelection is independent from the genotype representation.
BitFlipMutation requires the genotype representation to be indexable
(like a list). SinglePointCrossOver requires the genotype representation
to be indexable, sliceable and append-able. If the genotype representation
changes from numpy.ndarray, these operators need to be updated accordingly.
"""

__author__ = 'ntrolls@gmail.com (Shin Yoo)'

import random
import numpy
import solution


class SinglePointCrossover(object):
  """Picks a random position, crosses genes over at that index."""

  def Execute(self, parents):
    """Executes the crossover; takes a list of parent solutions."""
    c0 = parents[0].gene
    c1 = parents[1].gene

    if len(c0) != len(c1):
      raise ValueError('SinglePointCrossover: genes are of different lengths')

    crossoverpoint = random.randint(0, len(c0) - 1)

    nc0 = numpy.append(c0[0:crossoverpoint], c1[crossoverpoint:])
    nc1 = numpy.append(c1[0:crossoverpoint], c0[crossoverpoint:])

    offspring0 = solution.Solution(parents[0].problem, nc0)
    offspring1 = solution.Solution(parents[1].problem, nc1)
    return [offspring0, offspring1]


class BitFlipMutation(object):
  """Flip each digit with the probability of the mutation rate."""

  def __init__(self, rate):
    self.mutationrate = rate

  def Execute(self, sol):
    for i in xrange(len(sol.gene)):
      if random.random() < self.mutationrate:
        sol.gene[i] = not sol.gene[i]


class TwoArchiveSelection(object):
  """Picks a random solution from archives; each one gets the same chance."""

  def Execute(self, c_archive, d_archive):
    """Executes the selection."""
    archives = [c_archive, d_archive]
    parents = [None, None]
    for i in xrange(2):
      chosen = random.randint(0, 1)
      if chosen == 0 and not archives[0]:
        chosen = 1
      if chosen == 1 and not archives[1]:
        chosen = 0
      randomindex = random.randint(0, len(archives[chosen]) - 1)
      index = 0
      item = None
      for p in archives[chosen]:
        if index == randomindex:
          item = p
          break
        index += 1
      parents[i] = item
    return parents
